#-*- coding: utf-8 -*-

import pygame
import sys
from random import choice, random

# 全局常量
UNIT = 24 # 格子边长(偶数)
GRID = 25 # 横竖向格子数量
SIDEBAR = 250 # 侧边栏宽度
GAP = 10 # 边距
SCRW = UNIT*GRID + SIDEBAR + GAP # 窗口宽
SCRH = UNIT*GRID # 窗口高

# color
darkmode = False
BG_L = 255, 255, 255
BG_D = 44, 44, 44
FONT_L = 0, 0, 0
FONT_D = 255, 255, 255
GRID_L = 233, 233, 233
GRID_D = 33, 33, 33

# 游戏中的常量 （部分未实装）
FPS = 15 # 初始帧数(速度)
FPS_LO = 12 # 减速速度
FPS_HI = 18 # 加速速度
POINTS_LOSE = 1 # 每个时间失去点数
POINTS_LOSE_LESS = 0.5 # 每个时间失去更少点数
POINTS_LOSE_MORE = 5 # 每个时间失去更多点数
POINTS_LOSE_MORE_LESS = 3 # 前一行的加强
FOOD_POINTS = 50 # 每个食物得到分数
FOOD_POINTS_MORE = 60 # 更多分数
FOOD_POINTS_DBL = 100 # 双倍分数
FOOD_POINTS_HALF = 25 # 减半分数
COMBO_TIME = 30 # 连击时间
COMBO_TIME_LESS = 20 # 连击更少时间
COMBO_TIME_MORE = 40 # 连击更多时间
COMBO_REDUCE = 1 # 连击条在每个时间减少
COMBO_REDUCE_LOCKED = 0 # 连击锁定
BONUS_WAIT = 40 # 加成球出现间隔
BONUS_STAY = 40 # 加成球保留时间
BONUS_EFFECT_GOOD = 40 # 正面效果持续时间
BONUS_EFFECT_BAD = 40 # 负面效果持续时间
BONUS_EFFECT_BOTH = 40 # 双面效果持续时间
BIG_FOOD = 5 # 大食物代换
BIG_FOOD_PP = 8 # 前一行的加强
MAGNET_RADIUS = 1 # 磁铁半径
MAGNET_RADIUS_PP = 2 # 前一行的加强
SNAKE_CUT = 5 # 剪去蛇身长度
SNAKE_CUT_PP = 8 # 前一行的加强
TWO_FOOD_FREQ = 0 # 两个食物出现频率
TWO_FOOD_FREQ_PP = 0.2 # 前一行的加强
COMBO_CALC = lambda combo_number, points_per_food: (points_per_food * combo_number * (1 + combo_number / 10)) # 连击计算
COMBO_CALC_PP = lambda combo_number, points_per_food: (points_per_food * combo_number * (1 + combo_number / 10 * 1.2)) # 前一行的加强

def show_text(screen, pos, text, color, font_bold = False, font_size = 60, font_italic = False):   
    #获取系统字体，并设置文字大小  
    cur_font = pygame.font.SysFont("宋体", font_size)  
    #设置是否加粗属性  
    cur_font.set_bold(font_bold)  
    #设置是否斜体属性  
    cur_font.set_italic(font_italic)  
    #设置文字内容  
    text_fmt = cur_font.render(text, 1, color)  
    #绘制文字  
    screen.blit(text_fmt, pos)

'''蛇'''
class Snake:
    def __init__(self):
        self.dirction = pygame.K_RIGHT
        self.body = []
        self.length = 5
        for x in range(self.length):
            self.addnode()
        
    # 无论何时 都在前端增加蛇块
    def addnode(self):
        left,top = (0,0)
        if self.body:
            left,top = (self.body[0].left,self.body[0].top)
        node = pygame.Rect(left,top,UNIT,UNIT)
        if self.dirction == pygame.K_LEFT:
            node.left -= UNIT
        elif self.dirction == pygame.K_RIGHT:
            node.left += UNIT
        elif self.dirction == pygame.K_UP:
            node.top -= UNIT
        elif self.dirction == pygame.K_DOWN:
            node.top += UNIT
        self.body.insert(0,node)
        
    # 删除最后一个块
    def delnode(self): 
        if self.length > len(self.body):
            #应该长度大于实际长度，不删去节
            pass
        elif self.length < len(self.body):
            while self.length <= len(self.body):
                self.body.pop()
        else:
            #length is actual length
            self.body.pop()

    # 退后一格
    def goback(self):
        del self.body[0]
        
    # 死亡判断
    def isdead(self, if_shield, if_through):
        # 撞墙
        if self.body[0].x  not in range(UNIT*GRID):
            return (False if (if_shield or if_through) else True)
        if self.body[0].y  not in range(UNIT*GRID):
            return (False if (if_shield or if_through) else True)
        # 撞自己
        if self.body[0] in self.body[1:]:
            return (False if if_shield else True)
        return False
        
    # 移动！
    def move(self):
        self.addnode()
        self.delnode()
        
    # 改变方向 但是左右、上下不能被逆向改变
    def changedirection(self,curkey):
        LR = [pygame.K_LEFT,pygame.K_RIGHT]
        UD = [pygame.K_UP,pygame.K_DOWN]
        wasd = {pygame.K_a: pygame.K_LEFT, pygame.K_d: pygame.K_RIGHT, pygame.K_w: pygame.K_UP, pygame.K_s: pygame.K_DOWN}
        if curkey in wasd.keys():
            curkey = wasd[curkey]
        if curkey in LR+UD:
            if (curkey in LR) and (self.dirction in LR):
                return False
            if (curkey in UD) and (self.dirction in UD):
                return False
            self.dirction = curkey
            return True

'''皮肤'''

class skin:

    @staticmethod
    def skin0(surface, items):
        '''黑色'''
        for item in items:
            pygame.draw.rect(surface,(0,0,0),item,0)

    @staticmethod
    def skin1(surface:pygame.Surface, items:list):
        '''绿色'''
        for item in items:
            pygame.draw.rect(surface,(0,225,0),item,0)

    @staticmethod
    def skin2(surface, items):
        '''黄绿'''
        for item in items:
            pygame.draw.rect(surface,(0,225,0),(item[0]+4, item[1]+4, item[2]-8, item[3]-8),0)
            pygame.draw.rect(surface,(225,225,0),(item[0]+9, item[1]+9, item[2]-18, item[3]-18),0)
        pygame.draw.rect(surface,(0,225,0),(items[0][0]+2,items[0][1]+2,items[0][2]-4,items[0][3]-4),0)
        pygame.draw.rect(surface,(225,225,0),(items[0][0]+7,items[0][1]+7,items[0][2]-14,items[0][3]-14),0)

    @staticmethod
    def skin3(surface, items):
        '''红黄'''
        for item in items:
            pygame.draw.rect(surface,(225,225,0),(item[0]+2, item[1]+2, item[2]-4, item[3]-4),0)
            pygame.draw.rect(surface,(225,0,0),(item[0]+8, item[1]+8, item[2]-16, item[3]-16),0)

    @staticmethod
    def skin4(surface, items):
        '''黄色'''
        for item in items:
            pygame.draw.rect(surface,(225,225,0),(item[0]+2, item[1]+2, item[2]-4, item[3]-4),0)

    @staticmethod
    def skin5(surface, items):
        '''紫红'''
        for item in items:
            pygame.draw.rect(surface,(116,1,223),(item[0]+4, item[1]+4, item[2]-8, item[3]-8),0)
            pygame.draw.rect(surface,(255,0,191),(item[0]+9, item[1]+9, item[2]-18, item[3]-18),0)
        pygame.draw.rect(surface,(116,1,223),(items[0][0]+2,items[0][1]+2,items[0][2]-4,items[0][3]-4),0)
        pygame.draw.rect(surface,(225,0,191),(items[0][0]+7,items[0][1]+7,items[0][2]-14,items[0][3]-14),0)


def main():
    global darkmode, points, combo, combo_time, shield, magnet, time_slow, time_fast, vision_plus, combo_locked, frozen, burning, confused, poisoned, food_double, food_half, snake, obj_blocks
    pygame.init()
    screen = pygame.display.set_mode((SCRW, SCRH))
    clock = pygame.time.Clock()

    # 加成球(不为0时为有加成效果)
    shield = 0 # 护盾
    magnet = 0 # 磁铁
    time_slow = 0 # 时间缓慢
    time_fast = 0 # 时间加速
    vision_plus = 0 # 视觉加强
    combo_locked = 0 # 连击锁定
    frozen = 0 # 冷冻
    burning = 0 # 燃烧
    confused = 0 # 迷惑
    poisoned = 0 # 毒药
    food_double = 0 # 双倍食物分数
    food_half = 0 # 减半食物分数

    # 技能是否拥有
    bonus_stay_longer = False
    bonus_plus_plus = False
    bonus_freq_plus = False
    lose_less_points = False
    combo_more_time = False
    combo_more_bonus = False
    food_more_points = False
    snake_go_through = False

    # 游戏变量
    is_run = True
    snake = Snake()
    grids = []
    for i in range(GRID):
        for j in range(GRID):
            grids.append((i,j))
    food_blocks = []
    obj_blocks = []
    bonus_blocks = []
    points = 0
    lv = 0
    combo = 0
    combo_time = 0
    cd = BONUS_WAIT
    bonus_keep = 0
    effect_now = 'nil'
    skin_now = skin.skin1
    ungot_skill = ['加成球在屏幕上保留时间增加\n', '加成效果加强\n', '加成球的出现频率增加\n', 
    '蛇移动时扣分减少\n', '连击时间延长\n', '连击奖励增加\n', '食物加分小幅增加\n', 
    '有时会同时出现两个食物\n', '蛇可以从边界穿越到对边\n']
    got_skill = ''

    def get_food():
        global points, combo, combo_time
        print('\a', end='') # alarm
        points += (FOOD_POINTS_MORE if food_more_points else (FOOD_POINTS_DBL if food_double else (FOOD_POINTS_HALF if food_half else FOOD_POINTS)))
        combo += 1
        combo_time = (COMBO_TIME_MORE if combo_more_time else (COMBO_TIME_LESS if burning else COMBO_TIME))
        snake.length += 1

    # 获得加成 
    def big_food_bonus():
        '''Super food'''
        for i in range(BIG_FOOD_PP if bonus_plus_plus else BIG_FOOD):
            get_food()

    def time_run_slower():
        '''Time slow'''
        global time_slow
        time_slow += BONUS_EFFECT_GOOD

    def vision_plus_plus():
        '''Vision ++'''
        global vision_plus
        vision_plus += BONUS_EFFECT_GOOD

    def magnet_attract():
        '''Magnet'''
        global magnet
        magnet += BONUS_EFFECT_GOOD

    def one_time_shield():
        '''Shield'''
        global shield
        shield += BONUS_EFFECT_GOOD

    def cut_off_snake():
        '''Cut off snake'''
        global snake
        snake.length -= 5
        if snake.length < 5:
            snake.length = 5

    def food_dbl_points():
        '''Double the points'''
        global food_double
        food_double += BONUS_EFFECT_GOOD

    def combo_time_lock():
        '''Combo locked'''
        global combo_locked
        combo_locked += BONUS_EFFECT_GOOD

    def add_one_obj():
        '''Add object'''
        global obj_blocks
        randobj = choice(grids)
        newobj = (randobj[0]*UNIT, randobj[1]*UNIT, UNIT, UNIT)
        while newobj in snake.body or newobj in food_blocks:
            randobj = choice(grids)
            newobj = (randobj[0]*UNIT, randobj[1]*UNIT, UNIT, UNIT)
        obj_blocks.append(newobj)

    def time_run_faster():
        '''Time fast'''
        global time_fast
        time_fast += BONUS_EFFECT_BAD

    def food_half_points():
        '''Half the points'''
        global food_half
        food_half += BONUS_EFFECT_BAD

    def two_side_freeze():
        '''Frozen'''
        global frozen
        frozen += BONUS_EFFECT_BOTH
        big_food_bonus()

    def two_side_burn():
        '''Burning'''
        global burning
        burning += BONUS_EFFECT_BOTH
        big_food_bonus()

    def two_side_confuse():
        '''Confused'''
        global confused
        confused += BONUS_EFFECT_BOTH
        big_food_bonus()

    def two_side_poisonus():
        '''Poisioned'''
        global poisoned
        poisoned += BONUS_EFFECT_BOTH

    while True:
        changed=False
        # key event
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if not changed:
                    if snake.changedirection(event.key):
                        changed=True
                # 死后按space重新
                if event.key == pygame.K_SPACE and not is_run:
                    return main()
                if event.key == pygame.K_F12:
                    darkmode = not darkmode
                if event.key == pygame.K_1:
                    skin_now = skin.skin1
                if event.key == pygame.K_2:
                    skin_now = skin.skin2
                if event.key == pygame.K_3:
                    skin_now = skin.skin3
                if event.key == pygame.K_4:
                    skin_now = skin.skin4
                if event.key == pygame.K_5:
                    skin_now = skin.skin5
                if event.key == pygame.K_6:
                    skin_now = skin.skin0

        screen.fill(BG_D if darkmode else BG_L) # fill bg color

        
        if vision_plus >= 0: # draw vision++
            for i in range(0,GRID):
                for j in range(0,GRID):
                    pygame.draw.rect(screen, (GRID_D if darkmode else GRID_L), (i*UNIT, j*UNIT, UNIT, UNIT), 1)

        if is_run:
            points -= POINTS_LOSE_LESS if lose_less_points else (POINTS_LOSE_MORE_less if (poisoned and bonus_plus_plus) else (POINTS_LOSE_MORE if poisoned else POINTS_LOSE))
            if combo_time > 0:
                combo_time -= COMBO_REDUCE_LOCKED if (combo_locked or frozen) else COMBO_REDUCE
            else:
                points += int((FOOD_POINTS * combo * (1 + combo / 10)) // 1 - FOOD_POINTS * combo)
                combo = 0
            if cd > 0:
                cd -= 1
            elif bonus_keep == 0:
                bonus_keep = BONUS_STAY
                randbonus = choice(grids)
                newbonus = (randbonus[0]*UNIT, randbonus[1]*UNIT, UNIT, UNIT)
                while newbonus in snake.body or newbonus in obj_blocks:
                    randbonus = choice(grids)
                    newbonus = (randbonus[0]*UNIT, randbonus[1]*UNIT, UNIT, UNIT)
                bonus_blocks.append(newbonus)
                temp = random()
                if temp < 0.7:
                    bonus_blocks.append('good')
                elif temp > 0.9:
                    bonus_blocks.append('bad')
                else:
                    bonus_blocks.append('both')
            if bonus_keep >0:
                bonus_keep-=1
                if bonus_keep == 0:
                    cd = BONUS_WAIT
            else:
                bonus_blocks = []
            if shield > 0:
                shield -=1
                if shield == 0:
                    effect_now = 'nil'
            if magnet > 0:
                magnet -=1
                if magnet == 0:
                    effect_now = 'nil'
            if time_slow > 0:
                time_slow -=1
                if time_slow == 0:
                    effect_now = 'nil'
            if time_fast > 0:
                time_fast -=1
                if time_fast == 0:
                    effect_now = 'nil'
            if vision_plus > 0:
                vision_plus -=1
                if vision_plus == 0:
                    effect_now = 'nil'
            if combo_locked > 0:
                combo_locked -=1
                if combo_locked == 0:
                    effect_now = 'nil'
            if frozen > 0:
                frozen -=1
                if frozen == 0:
                    effect_now = 'nil'
            if burning > 0:
                burning -=1
                if burning == 0:
                    effect_now = 'nil'
            if confused > 0:
                confused -=1
                if confused == 0:
                    effect_now = 'nil'
            if poisoned > 0:
                poisoned -=1
                if poisoned == 0:
                    effect_now = 'nil'
            if food_double > 0:
                food_double -=1
                if food_double == 0:
                    effect_now = 'nil'
            if food_half > 0:
                food_half -=1
                if food_half == 0:
                    effect_now = 'nil'
            while points >= 500:
                points -= 500
                lv += 1
                combo_time = 1
                for i in range(lv*5):
                    add_one_obj()
            if random() <= 0.01:
                add_one_obj()
            snake.move()
            skin_now(screen, snake.body) # draw snake

        is_run = not snake.isdead(shield, snake_go_through)
        for obj in obj_blocks: # hit obj
            if obj in snake.body:
                is_run = False
        if not is_run: # show die text
            show_text(screen,(100,200),'GAME OVER!',(227,29,18),False,100)
            show_text(screen,(150,260),'press space to restart...',(147, 147, 147),False,30)

        for food in food_blocks: # eat food
            if food in snake.body:
                get_food()
                del food_blocks[food_blocks.index(food)]
        
        if bonus_blocks != []:
            # eat bonus
            if bonus_blocks[0] in snake.body:
                if bonus_blocks[1] == 'good':
                    temp = choice([big_food_bonus, time_run_slower, vision_plus_plus, magnet_attract, cut_off_snake, food_dbl_points, combo_time_lock])#, one_time_shield
                elif bonus_blocks[1] == 'bad':
                    temp = choice([add_one_obj, time_run_faster, food_half_points])
                elif bonus_blocks[1] == 'both':
                    temp = choice([two_side_freeze, two_side_burn, two_side_confuse, two_side_poisonus])
                effect_now = temp.__doc__
                temp()
                bonus_blocks = []
                cd = BONUS_WAIT
                
        
        for obj in obj_blocks: # hit obj
            if obj in snake.body:
                is_run = False

        if food_blocks == []:
            randfood = choice(grids)
            newfood = (randfood[0]*UNIT, randfood[1]*UNIT, UNIT, UNIT)
            while newfood in snake.body or newfood in obj_blocks:
                randfood = choice(grids)
                newfood = (randfood[0]*UNIT, randfood[1]*UNIT, UNIT, UNIT)
            food_blocks.append(newfood)
        
        for i in food_blocks:
            pygame.draw.circle(screen,(174, 174, 0),(i[0]+12,i[1]+12),5,0)
        for i in obj_blocks:
            pygame.draw.rect(screen, (174, 174, 174), i, 0)
        if bonus_blocks != []:
            if bonus_blocks[1] == 'good':
                temp = 174,174,0
            elif bonus_blocks[1] == 'bad':
                temp = 174,174,0
            else:
                temp = 174,174,0
            pygame.draw.circle(screen,temp,(bonus_blocks[0][0]+12,bonus_blocks[0][1]+12),12,0)

        pygame.draw.rect(screen, (255, 255, 200), (UNIT*GRID, 0, SIDEBAR+GAP, SCRH), 0)
        show_text(screen, (UNIT*GRID+GAP, GAP), 'Points: '+str(points+500*lv), (0, 0, 0), False, 40, False)
        show_text(screen, (UNIT*GRID+GAP, GAP+40), 'Level: '+str(lv), (0, 0, 0), False, 40, False)
        show_text(screen, (UNIT*GRID+GAP, GAP+80), ('Combo: '), (0, 0, 0), False, 30, False)
        show_text(screen, (UNIT*GRID+GAP, GAP+110), ('Effect: %s'%effect_now), (0, 0, 0), False, 30, False)
        pygame.draw.rect(screen, (0, 0, 0), (UNIT*GRID+GAP+80, GAP+80, combo_time*3, 20), 0)
        show_text(screen, (UNIT*GRID+GAP, GAP+140), ('Next Bonus: '), (0, 0, 0), False, 30, False)
        pygame.draw.rect(screen, (0, 0, 0), (UNIT*GRID+GAP+130, GAP+140, cd*3, 20), 0)
        show_text(screen, (UNIT*GRID+GAP, GAP+170), ('Bonus stay: '), (0, 0, 0), False, 30, False)
        pygame.draw.rect(screen, (0, 0, 0), (UNIT*GRID+GAP+130, GAP+170, bonus_keep*3, 20), 0)
        show_text(screen, (UNIT*GRID+GAP, GAP+200), ('Effect left: '), (0, 0, 0), False, 30, False)
        pygame.draw.rect(screen, (0, 0, 0), (UNIT*GRID+GAP+130, GAP+200, sum((shield, magnet, time_slow, time_fast, vision_plus, combo_locked, frozen, burning, confused, poisoned, food_double, food_half))*3, 20), 0)

        pygame.display.update()
        clock.tick(FPS_HI if time_fast else (FPS_LO if (time_slow or poisoned) else FPS))


if __name__ == "__main__":
    main()

